﻿using LinearAlgebra;
using LinearAlgebra.MatrixAlgebra;
using LinearAlgebra.VectorAlgebra;
using System;
using System.Collections.Generic;
using System.IO;
using static LinearAlgebra.UnlinearFunctionHelper;

namespace NeuralNetwork
{
    public class NNetwork
    {
        /// <summary>
        /// 输入层节点个数
        /// </summary>
        public int InputeNodeCount { get; private set; }
        /// <summary>
        /// 隐藏层节点个数
        /// </summary>
        public int[] HiddenNodeCount { get; private set; }
        /// <summary>
        /// 输出层节点个数
        /// </summary>
        public int OutputNodeCount { get; private set; }

        ///// <summary>
        ///// 输入层 -> 隐藏层 权重矩阵
        ///// </summary>
        //public Matrix Wight_ih;
        ///// <summary>
        ///// 隐藏层间
        ///// </summary>
        public Matrix[] Weight_h;
        /// <summary>
        /// 隐藏层 -> 输出层 权重矩阵
        /// </summary>
        public Matrix Wight_ho;


        //public Matrix Offset_ih;
        public Matrix[] Offset_h;
        public Matrix Offset_ho;

        /// <summary>
        /// 学习率
        /// </summary>
        public double LearningRate { get; set; }

        /// <summary>
        /// 激活函数（这里全局都是这个激活函数）
        /// </summary>
        public IFunction ActivationFunction;

        /// <summary>
        /// 最大准确率记录
        /// </summary>
        public double MaxSuccessRate;

        private NNetwork() { }
        public NNetwork(int inputNodeCount, int[] hiddenNodeCounts, int outputNodeCount, double learningRate)
        {
            InputeNodeCount = inputNodeCount;
            HiddenNodeCount = hiddenNodeCounts;
            OutputNodeCount = outputNodeCount;
            LearningRate = learningRate;

            InitNetwork();
        }

        private void InitNetwork()
        {
            ActivationFunction = new Sigmoid_01();// 指定激活函数

            int seed = DateTime.Now.Millisecond;// 随机种子，用来初始化最初的权值

            // 初始化权值，范围在 +-sqrt(上一层的节点数)
            //Wight_ih = 2 * (1 / Math.Sqrt(InputeNodeCount)) * (Matrix.Random(HiddenNodeCount[0], InputeNodeCount, ++seed) - 0.5);
            //Offset_ih = 2 * (1 / Math.Sqrt(InputeNodeCount)) * (Matrix.Random(HiddenNodeCount[0], 1, ++seed) - 0.5);

            Weight_h = new Matrix[HiddenNodeCount.Length];
            Offset_h = new Matrix[HiddenNodeCount.Length];
            int pRow = InputeNodeCount;
            for (int i = 0; i < HiddenNodeCount.Length; i++)
            {
                Weight_h[i] = 2 * (1 / Math.Sqrt(pRow)) * (Matrix.Random(HiddenNodeCount[i], pRow, ++seed) - 0.5);
                Offset_h[i] = 2 * (1 / Math.Sqrt(pRow)) * (Matrix.Random(HiddenNodeCount[i], 1, ++seed) - 0.5);
                pRow = HiddenNodeCount[i];
            }

            Wight_ho = 2 * (1 / Math.Sqrt(HiddenNodeCount[HiddenNodeCount.Length - 1])) * (Matrix.Random(OutputNodeCount, HiddenNodeCount[HiddenNodeCount.Length - 1], ++seed) - 0.5);
            Offset_ho = 2 * (1 / Math.Sqrt(HiddenNodeCount[HiddenNodeCount.Length - 1])) * (Matrix.Random(OutputNodeCount, 1, ++seed) - 0.5);
        }

        /// <summary>
        /// 训练
        /// </summary>
        /// <param name="inputList">输入数据</param>
        /// <param name="targetList">目标输出</param>
        public void Train(Vector inputList, Vector targetList)
        {
            if (inputList.vectorType == VectorType.Row) inputList.vectorType = VectorType.Column;// 输入应为列向量
            if (targetList.vectorType == VectorType.Row) targetList.vectorType = VectorType.Column;// 输入应为列向量

            Matrix mInput = inputList;
            Matrix mTarget = targetList;

            /* ★ 这里的 * 是矩阵乘法 */

            //var xHidden = Wight_ih * mInput + Offset_ih;
            //var oHiddden = ActivationFunction.Solve(xHidden);// 隐藏层输出
            //var xOutput = Wight_ho * oHiddden + Offset_ho;
            //var oOutput = ActivationFunction.Solve(xOutput);// 输出层输出
            //Matrix oHidden = mInput;

            // 正向传播
            Matrix[] outputInLevel_X = new Matrix[Weight_h.Length];
            Matrix[] outputInLevel_Y = new Matrix[Weight_h.Length];
            for (int i = 0; i < Weight_h.Length; i++)
            {
                if (i == 0)
                    outputInLevel_X[i] = Weight_h[i] * mInput + Offset_h[i];
                else outputInLevel_X[i] = Weight_h[i] * outputInLevel_Y[i - 1] + Offset_h[i];

                outputInLevel_Y[i] = ActivationFunction.Solve(outputInLevel_X[i]);
            }
            var xOutput = Wight_ho * outputInLevel_Y[outputInLevel_Y.Length - 1] + Offset_ho;
            var oOutput = ActivationFunction.Solve(xOutput);

            Matrix outputErr = (mTarget - oOutput);
            Matrix hiddenErr = Wight_ho.Transpose() * outputErr;

            Matrix[] err = new Matrix[Weight_h.Length + 1];
            err[err.Length - 1] = hiddenErr;
            var oe = hiddenErr;
            for (int i = err.Length - 2; i >= 1; i--)
            {
                if (i == err.Length - 1) oe = hiddenErr;
                else oe = err[i + 1];
                err[i] = Weight_h[i].Transpose() * oe;
            }


            // 计算权值差
            var delta_ho = LearningRate * MatrixComputation.Hadamard(outputErr, ActivationFunction.Derivative.Solve(xOutput));
            Matrix delWight_ho = delta_ho * outputInLevel_Y[outputInLevel_Y.Length - 1].Transpose();
            Offset_ho += delta_ho;// 更新偏置
            Wight_ho += delWight_ho;// 更新权值
            Matrix inp = mInput;
            for (int i = Weight_h.Length - 1; i >= 0; i--)
            {
                if (i > 0) inp = outputInLevel_Y[i - 1];
                else inp = mInput;
                var delta_ih = LearningRate * MatrixComputation.Hadamard(err[i + 1], ActivationFunction.Derivative.Solve(outputInLevel_X[i]));
                Matrix delWight_ih = delta_ih * inp.Transpose();
                Offset_h[i] += delta_ih;
                Weight_h[i] += delWight_ih;
            }

            //var delta_ih = LearningRate * MatrixComputation.Hadamard(hiddenErr, ActivationFunction.Derivative.Solve(xHidden));
            //Matrix delWight_ih = delta_ih * mInput.Transpose();
            //Offset_ih += delta_ih;
            //Wight_ih += delWight_ih;


        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <param name="input">输入，列向量</param>
        public Vector Query(Vector input)
        {
            if (input.vectorType == VectorType.Row) input.vectorType = VectorType.Column;// 输入应为列向量

            //var iHiddden = ActivationFunction.Solve(Wight_ih * input + Offset_ih);
            Matrix oHidden = input;
            for (int i = 0; i < Weight_h.Length; i++)
            {
                oHidden = ActivationFunction.Solve(Weight_h[i] * oHidden + Offset_h[i]);
            }
            var oOutput = ActivationFunction.Solve(Wight_ho * oHidden + Offset_ho);

            return oOutput;
        }
        public Vector Query(params double[] inputs)
        {
            return Query(new Vector(inputs, VectorType.Column));
        }

        public void Save(string path)
        {
            DirectoryInfo targetDir;
            if (!Directory.Exists(path))
            {
                targetDir = Directory.CreateDirectory(path);
            }
            else
            {
                targetDir = new DirectoryInfo(path);
            }

            for (int i = 0; i < Weight_h.Length; i++)
            {
                Weight_h[i].Save(Path.Combine(targetDir.Name, string.Format("Wight_h_{0}.mat", i)));
                Offset_h[i].Save(Path.Combine(targetDir.Name, string.Format("Offset_h_{0}.mat", i)));
            }

            //Wight_ih.Save(Path.Combine(targetDir.Name, "Wight_ih.mat"));
            //Offset_ih.Save(Path.Combine(targetDir.Name, "Offset_h.mat"));
            Wight_ho.Save(Path.Combine(targetDir.Name, "Wight_ho.mat"));
            Offset_ho.Save(Path.Combine(targetDir.Name, "Offset_o.mat"));

            NNetworkData data = new NNetworkData();
            data.InputNodeCount = InputeNodeCount;
            data.HidenNodeCount = HiddenNodeCount;
            data.OutputNodeCount = OutputNodeCount;
            data.LearningRate = LearningRate;
            data.ActivationFunctionName = ActivationFunction.GetType().AssemblyQualifiedName;
            data.ActivationFunctionArgs = new string[] { };

            data.HiddenWightMatrixFileName = new string[Weight_h.Length];
            data.HiddenOffsetMatrixFileName = new string[Weight_h.Length];
            for (int i = 0; i < Weight_h.Length; i++)
            {
                data.HiddenWightMatrixFileName[i] = string.Format("Wight_h_{0}.mat", i);
                data.HiddenOffsetMatrixFileName[i] = string.Format("Offset_h_{0}.mat", i);
            }

            data.OutputWightMatrixFileName = "Wight_ho.mat";
            data.OutputOffsetMatrixFileName = "Offset_o.mat";
            data.MaxSuccessRate = MaxSuccessRate;


            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(NNetworkData));
            using (FileStream stream = new FileStream(Path.Combine(targetDir.Name, "config.xml"), FileMode.Create))
            {
                serializer.Serialize(stream, data);
            }
        }

        public static NNetwork Load(string dirPath)
        {
            NNetwork nn = new NNetwork();
            NNetworkData data;
            System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(NNetworkData));
            using (FileStream stream = new FileStream(Path.Combine(dirPath, "config.xml"), FileMode.Open))
            {
                data = serializer.Deserialize(stream) as NNetworkData;
            }

            nn.InputeNodeCount = data.InputNodeCount;
            nn.HiddenNodeCount = data.HidenNodeCount;
            nn.OutputNodeCount = data.OutputNodeCount;
            nn.LearningRate = data.LearningRate;
            var type = Type.GetType(data.ActivationFunctionName);
            nn.ActivationFunction = Activator.CreateInstance(type) as IFunction;// 这里要求所有
            nn.ActivationFunction.SetArgs(data.ActivationFunctionArgs);

            nn.Weight_h = new Matrix[nn.HiddenNodeCount.Length];
            nn.Offset_h = new Matrix[nn.HiddenNodeCount.Length];
            for (int i = 0; i < nn.Weight_h.Length; i++)
            {
                nn.Weight_h[i] = Matrix.Load(Path.Combine(dirPath, data.HiddenWightMatrixFileName[0]));
                nn.Offset_h[i] = Matrix.Load(Path.Combine(dirPath, data.HiddenOffsetMatrixFileName[0]));
            }

            nn.Wight_ho = Matrix.Load(Path.Combine(dirPath, data.OutputWightMatrixFileName));
            nn.Offset_ho = Matrix.Load(Path.Combine(dirPath, data.OutputOffsetMatrixFileName));
            nn.MaxSuccessRate = data.MaxSuccessRate;

            return nn;
        }
    }

    [Serializable]
    public class NNetworkData
    {
        public int InputNodeCount;
        public int[] HidenNodeCount;
        public int OutputNodeCount;
        public double LearningRate;
        public string ActivationFunctionName;
        public string[] ActivationFunctionArgs;

        public string[] HiddenWightMatrixFileName;
        public string OutputWightMatrixFileName;
        public string[] HiddenOffsetMatrixFileName;
        public string OutputOffsetMatrixFileName;

        public double MaxSuccessRate;
    }
}
